using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Security.Permissions;
using System.Windows.Forms;
using Schedulr.Configuration;
using Schedulr.Helpers;
using Schedulr.Properties;
using Schedulr.Tools;

namespace Schedulr.Controls
{
    /// <summary>
    /// Allows the user to edit the picture queue.
    /// </summary>
    public partial class QueuedPicturesEditor : UserControl
    {
        #region Fields

        private Dictionary<string, Image> previewCache = new Dictionary<string, Image>();
        private bool editMode;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="QueuedPicturesEditor"/> class.
        /// </summary>
        public QueuedPicturesEditor()
        {
            InitializeComponent();
            Settings.Default.PropertyChanged += delegate
            {
                UpdateUI();
            };
            FlickrServer.CurrentUserChanged += delegate
            {
                UpdateUI();
            };
        }

        #endregion

        #region Initialize

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        /// <param name="pictures">The queued pictures.</param>
        internal void Initialize(BindingList<Picture> pictures)
        {
            this.editMode = true;
            this.queuedPicturesBindingSource.DataSource = pictures;
            UpdateUI();
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        /// <param name="pictures">The queued pictures.</param>
        internal void Initialize(BindingList<UploadedPicture> pictures)
        {
            this.editMode = false;
            this.queuedPicturesBindingSource.DataSource = pictures;
            UpdateUI();
        }

        #endregion

        #region UpdateUI

        private void UpdateUI()
        {
            if (this.Preview.Visible != Settings.Default.ShowInlinePreviews)
            {
                this.Preview.Visible = Settings.Default.ShowInlinePreviews;
            }

            this.addButton.Text = (this.editMode ? "&Add..." : "En&queue");

            this.uploadedDataGridViewTextBoxColumn.Visible = !this.editMode;
            this.moveToTopButton.Enabled = this.editMode;
            this.moveUpButton.Enabled = this.editMode;
            this.moveDownButton.Enabled = this.editMode;
            this.moveToBottomButton.Enabled = this.editMode;
            this.shuffleButton.Enabled = this.editMode;
            this.uploadNowButton.Enabled = this.editMode;
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Handles the CurrentItemChanged event of the queuedPicturesBindingSource control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void queuedPicturesBindingSource_CurrentItemChanged(object sender, EventArgs e)
        {
            Picture currentPicture = (Picture)this.queuedPicturesBindingSource.Current;
            if (currentPicture != null && this.previewPictureBox.ImageLocation != currentPicture.FileName && File.Exists(currentPicture.FileName))
            {
                this.previewPictureBox.ImageLocation = currentPicture.FileName;
            }
            else
            {
                this.previewPictureBox.ImageLocation = null;
            }
        }

        /// <summary>
        /// Handles the Click event of the deleteButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void deleteButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();
            foreach (DataGridViewRow rowToDelete in GridHelper.GetSelectedRows(this.picturesDataGridView, false))
            {
                this.picturesDataGridView.Rows.Remove(rowToDelete);
            }
        }

        /// <summary>
        /// Handles the Click event of the addButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void addButton_Click(object sender, EventArgs e)
        {
            if (this.editMode)
            {
                DialogResult result = this.addPicturesOpenFileDialog.ShowDialog(this);
                if (result == DialogResult.OK)
                {
                    AddFiles(this.addPicturesOpenFileDialog.FileNames);
                }
            }
            else
            {
                IList<Picture> selectedPictures = GridHelper.GetSelectedItems<Picture>(this.picturesDataGridView);
                foreach (Picture selectedPicture in selectedPictures)
                {
                    SchedulrConfiguration.Instance.QueuedPictures.Add(new Picture(selectedPicture));
                }
            }
        }

        /// <summary>
        /// Handles the Click event of the uploadNowButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void uploadNowButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();

            IList<Picture> selectedPictures = GridHelper.GetSelectedItems<Picture>(this.picturesDataGridView);
            if (selectedPictures.Count > 0)
            {
                string message = string.Format(CultureInfo.CurrentCulture, "Are you sure you want to upload the currently selected picture(s) to Flickr?{0}{0}Note that only the selected pictures will be uploaded, regardless of their setting to \"Upload together with next picture in queue\".", Environment.NewLine);
                string caption = string.Format(CultureInfo.CurrentCulture, "Uploading {0} picture(s)...", selectedPictures.Count);
                DialogResult result = MessageBox.Show(message, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
                if (result == DialogResult.Yes)
                {
                    using (UIActionScope scope = new UIActionScope(caption))
                    {
                        FlickrServer.UploadPictures(selectedPictures);
                    }
                }

                this.queuedPicturesBindingSource.ResetBindings(false);
            }
        }

        /// <summary>
        /// Handles the Click event of the moveUpButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void moveUpButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();
            List<int> selectedRowIndices = new List<int>();
            IList<DataGridViewRow> selectedRows = GridHelper.GetSelectedRows(this.picturesDataGridView, true);
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                selectedRowIndices.Add(rowToMove.Index - 1);
            }
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                Picture pictureToMove = (Picture)rowToMove.DataBoundItem;
                if (pictureToMove != null)
                {
                    int oldIndex = this.queuedPicturesBindingSource.IndexOf(pictureToMove);
                    if (oldIndex > 0)
                    {
                        this.queuedPicturesBindingSource.RemoveAt(oldIndex);
                        this.queuedPicturesBindingSource.Insert(oldIndex - 1, pictureToMove);
                    }
                }
            }
            foreach (DataGridViewRow row in this.picturesDataGridView.Rows)
            {
                row.Selected = (selectedRowIndices.Contains(row.Index));
            }
        }

        /// <summary>
        /// Handles the Click event of the moveDownButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void moveDownButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();
            List<int> selectedRowIndices = new List<int>();
            IList<DataGridViewRow> selectedRows = GridHelper.GetSelectedRows(this.picturesDataGridView, false);
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                selectedRowIndices.Add(rowToMove.Index + 1);
            }
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                Picture pictureToMove = (Picture)rowToMove.DataBoundItem;
                if (pictureToMove != null)
                {
                    int oldIndex = this.queuedPicturesBindingSource.IndexOf(pictureToMove);
                    if (oldIndex < this.queuedPicturesBindingSource.Count - 1)
                    {
                        this.queuedPicturesBindingSource.RemoveAt(oldIndex);
                        this.queuedPicturesBindingSource.Insert(oldIndex + 1, pictureToMove);
                    }
                }
            }
            foreach (DataGridViewRow row in this.picturesDataGridView.Rows)
            {
                row.Selected = (selectedRowIndices.Contains(row.Index));
            }
        }

        /// <summary>
        /// Handles the Click event of the moveToBottomButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void moveToBottomButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();
            IList<DataGridViewRow> selectedRows = GridHelper.GetSelectedRows(this.picturesDataGridView, true);
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                Picture pictureToMove = (Picture)rowToMove.DataBoundItem;
                if (pictureToMove != null)
                {
                    int oldIndex = this.queuedPicturesBindingSource.IndexOf(pictureToMove);
                    this.queuedPicturesBindingSource.RemoveAt(oldIndex);
                    this.queuedPicturesBindingSource.Insert(this.queuedPicturesBindingSource.Count, pictureToMove);
                }
            }
            for (int i = 0; i < this.picturesDataGridView.Rows.Count - 1; i++)
            {
                this.picturesDataGridView.Rows[i].Selected = (i > this.queuedPicturesBindingSource.Count - selectedRows.Count - 1);
            }
        }

        /// <summary>
        /// Handles the Click event of the moveToTopButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void moveToTopButton_Click(object sender, EventArgs e)
        {
            this.queuedPicturesBindingSource.EndEdit();
            IList<DataGridViewRow> selectedRows = GridHelper.GetSelectedRows(this.picturesDataGridView, false);
            foreach (DataGridViewRow rowToMove in selectedRows)
            {
                Picture pictureToMove = (Picture)rowToMove.DataBoundItem;
                if (pictureToMove != null)
                {
                    int oldIndex = this.queuedPicturesBindingSource.IndexOf(pictureToMove);
                    this.queuedPicturesBindingSource.RemoveAt(oldIndex);
                    this.queuedPicturesBindingSource.Insert(0, pictureToMove);
                }
            }
            for (int i = 0; i < this.picturesDataGridView.Rows.Count - 1; i++)
            {
                this.picturesDataGridView.Rows[i].Selected = (i < selectedRows.Count);
            }
        }

        /// <summary>
        /// Handles the Click event of the shuffleButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void shuffleButton_Click(object sender, EventArgs e)
        {
            Random rng = new Random();
            if (this.queuedPicturesBindingSource.Count > 1)
            {
                for (int i = this.queuedPicturesBindingSource.Count - 1; i > 0; i--)
                {
                    int pos = rng.Next(i + 1);
                    object tmp = this.queuedPicturesBindingSource[i];
                    this.queuedPicturesBindingSource[i] = this.queuedPicturesBindingSource[pos];
                    this.queuedPicturesBindingSource[pos] = tmp;
                }
            }
        }

        /// <summary>
        /// Handles the RowPrePaint event of the picturesDataGridView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DataGridViewRowPrePaintEventArgs"/> instance containing the event data.</param>
        private void picturesDataGridView_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
        {
            // Do not perform any fancyness when not in edit mode.
            // Batch group rendering in colors cannot be determined properly and row errors are not important anymore.
            if (!this.editMode)
            {
                return;
            }
            if (e.RowIndex >= 0)
            {
                // Show batch groups in the same color.
                DataGridViewRow prevRow = null;
                Picture prevPicture = null;
                if (e.RowIndex > 0)
                {
                    prevRow = this.picturesDataGridView.Rows[e.RowIndex - 1];
                    prevPicture = (Picture)prevRow.DataBoundItem;
                }

                DataGridViewRow row = this.picturesDataGridView.Rows[e.RowIndex];
                Picture picture = (Picture)row.DataBoundItem;
                if (picture != null)
                {
                    Color backColor = Color.White;
                    if (prevPicture != null)
                    {
                        if (prevPicture.UploadWithNext)
                        {
                            backColor = prevRow.DefaultCellStyle.BackColor;
                        }
                        else
                        {
                            backColor = (prevRow.DefaultCellStyle.BackColor == Color.White ? Color.AntiqueWhite : Color.White);
                        }
                    }
                    row.DefaultCellStyle.BackColor = backColor;

                    // Check if the file size is too large for the user's quota.
                    if (FlickrServer.CurrentUserInfo != null && picture.FileSize > FlickrServer.CurrentUserInfo.MaxFileSize)
                    {
                        row.DefaultCellStyle.ForeColor = Color.Red;
                        row.ErrorText = "The file is larger than the maximum file size you are allowed to upload.";
                    }
                    else
                    {
                        row.DefaultCellStyle.ForeColor = Color.Empty;
                        row.ErrorText = null;
                    }
                }
            }
        }

        /// <summary>
        /// Handles the Click event of the previewPictureBox control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void previewPictureBox_Click(object sender, EventArgs e)
        {
            string picturePath = this.previewPictureBox.ImageLocation;
            if (!string.IsNullOrEmpty(picturePath) && File.Exists(picturePath))
            {
                Process.Start(picturePath);
            }
        }

        /// <summary>
        /// Handles the SelectionChanged event of the picturesDataGridView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void picturesDataGridView_SelectionChanged(object sender, EventArgs e)
        {
            IList<Picture> selectedPictures = GridHelper.GetSelectedItems<Picture>(this.picturesDataGridView);

            string fileName = null;
            string fileSize = null;

            if (selectedPictures.Count == 1)
            {
                Picture firstPicture = selectedPictures[0];
                fileName = firstPicture.FileName;
                fileSize = firstPicture.FileSizeString;
            }
            else if (selectedPictures.Count > 1)
            {
                fileName = string.Format(CultureInfo.CurrentCulture, "<{0} pictures selected>", selectedPictures.Count);
                long totalFileSize = 0;
                foreach (Picture picture in selectedPictures)
                {
                    totalFileSize += picture.FileSize;
                }
                fileSize = Converter.GetBytesAsString(totalFileSize);
            }

            this.fileNameTextBox.Text = fileName;
            this.fileSizeTextBox.Text = fileSize;

            this.selectedPicturesEditor.SetPictures(selectedPictures);
            this.selectedPicturesEditor.Enabled = (this.editMode && selectedPictures.Count > 0);
        }

        /// <summary>
        /// Handles the PropertyChanged event of the selectedPicturesEditor control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void selectedPicturesEditor_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == Picture.PropertyNameUploadWithNext)
            {
                this.picturesDataGridView.Refresh();
            }
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Adds the given files to the upload queue.
        /// </summary>
        /// <param name="files">The files to add to the upload queue.</param>
        private void AddFiles(string[] files)
        {
            using (UIActionScope scope = new UIActionScope("Adding files..."))
            {
                foreach (string file in files)
                {
                    if (File.Exists(file))
                    {
                        using (UIActionScope innerScope = new UIActionScope("Adding file: " + file))
                        {
                            string fullPath = Path.GetFullPath(file);
                            Picture pictureToAdd = PictureHelper.GetPicture(fullPath);
                            this.queuedPicturesBindingSource.List.Add(pictureToAdd);
                        }
                    }
                }
            }
        }

        #endregion

        #region Drag & Drop Event Handling

        private void QueuedPicturesEditor_DragDrop(object sender, DragEventArgs e)
        {
            string[] fileNames = e.Data.GetData(DataFormats.FileDrop) as string[];
            if (fileNames != null && fileNames.Length > 0)
            {
                AddFiles(fileNames);
            }
        }

        private void QueuedPicturesEditor_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                e.Effect = DragDropEffects.Copy;
            }
            else
            {
                e.Effect = DragDropEffects.None;
            }
        }

        #endregion

        #region Key Handling

        /// <summary>
        /// Processes the dialog key.
        /// </summary>
        /// <param name="keyData">The key data.</param>
        /// <returns>true if the dialog key was processed, false otherwise.</returns>
        [UIPermission(SecurityAction.LinkDemand, Window = UIPermissionWindow.AllWindows)]
        protected override bool ProcessDialogKey(Keys keyData)
        {
            if ((keyData & Keys.Alt) == Keys.Alt)
            {
                if ((keyData & Keys.Up) == Keys.Up)
                {
                    this.queuedPicturesBindingSource.MovePrevious();
                    return true;
                }
                if ((keyData & Keys.Down) == Keys.Down)
                {
                    this.queuedPicturesBindingSource.MoveNext();
                    return true;
                }
            }
            return base.ProcessDialogKey(keyData);
        }

        #endregion

        #region Inline Grid Preview Handling

        /// <summary>
        /// Handles the CellFormatting event of the picturesDataGridView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DataGridViewCellFormattingEventArgs"/> instance containing the event data.</param>
        private void picturesDataGridView_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            if (!e.FormattingApplied && e.DesiredType == typeof(Image))
            {
                DataGridViewCell cell = this.picturesDataGridView[e.ColumnIndex, e.RowIndex];
                if (cell != null && cell.Value is string)
                {
                    string fileName = (string)cell.Value;
                    if (!previewCache.ContainsKey(fileName))
                    {
                        previewCache[fileName] = new Bitmap(fileName);
                    }
                    e.Value = previewCache[fileName];
                }
            }
        }

        /// <summary>
        /// Handles the DataError event of the picturesDataGridView control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.DataGridViewDataErrorEventArgs"/> instance containing the event data.</param>
        private void picturesDataGridView_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            if (e.Exception is FormatException)
            {
                // This exception is thrown for some reason when a new row is added.
                e.ThrowException = false;
            }
        }

        #endregion
    }
}